package xyz.eden.boot.common.web.tool;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;

public final class SpringBeanTool {

    private static final SpringBeanTool INSTANCE = new SpringBeanTool();
    private ApplicationContext applicationContext;

    public SpringBeanTool() {
    }

    public SpringBeanTool getInstance() {
        return INSTANCE;
    }

    /**
     * Destroy bean by className
     *
     * @param className className
     */
    public void destroyBean(final String className) {

        String beanName = this.getBeanName(className);
        if (StringUtils.isBlank(beanName)) {
            return;
        }
        DefaultListableBeanFactory beanFactory = getBeanFactory();
        if (beanFactory.containsBean(beanName)) {
            beanFactory.destroySingleton(beanName);
            beanFactory.removeBeanDefinition(beanName);
        }
    }

    /**
     * Exist spring bean boolean.
     * Only applies to successful beans registered by the {@linkplain #registerBean(BeanDefinition, ClassLoader)} method.
     *
     * @param className the class name
     * @return the boolean
     */
    public boolean existBean(final String className) {
        String beanName = this.getBeanName(className);
        if (StringUtils.isBlank(beanName)) {
            return false;
        }
        return this.applicationContext.containsBean(beanName);
    }

    /**
     * Register bean
     *
     * @param beanDefinition
     * @param classLoader
     * @return
     */
    public String registerBean(final BeanDefinition beanDefinition, final ClassLoader classLoader) {
        String beanClassName = beanDefinition.getBeanClassName();
        String beanName;
        if (StringUtils.isBlank(beanClassName) || StringUtils.isBlank(beanName = this.getBeanName(beanClassName))) {
            throw new NullPointerException("beanDefinition.beanClassName is null");
        }
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
        beanFactory.setBeanClassLoader(classLoader);
        beanFactory.registerBeanDefinition(beanName, beanDefinition);
        return beanName;
    }

    /**
     * Get bean by className
     *
     * @param className
     * @param <T>
     * @return
     */
    @SuppressWarnings("all")
    public <T> T getBeanByClassName(final String className) {
        // get beanName by className
        String beanName = this.getBeanName(className);
        if (StringUtils.isBlank(beanName)) {
            return null;
        }
        try {
            return this.getBean(beanName);
        } catch (BeansException e) {
            return null;
        }
    }

    /**
     * get bean by name
     *
     * @param beanName
     * @param <T>
     * @return
     */
    @SuppressWarnings("all")
    public <T> T getBean(final String beanName) {
        return (T) applicationContext.getBean(beanName);
    }

    /**
     * get bean by class
     *
     * @param type
     * @param <T>
     * @return
     */
    public <T> T getBean(final Class<T> type) {
        return applicationContext.getBean(type);
    }

    /**
     * Convert class to bean name
     *
     * @param className
     * @return
     */
    private String getBeanName(final String className) {
        if (StringUtils.isBlank(className)) {
            return null;
        }
        String name = className.substring(className.lastIndexOf(".") + 1);
        String start = name.substring(0, 1);
        String end = name.substring(1);
        return start.toLowerCase() + end;
    }

    /**
     * Get Bean Factory
     *
     * @return
     */
    private DefaultListableBeanFactory getBeanFactory() {
        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
        return (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
    }

    /**
     * set applicationContext
     *
     * @param applicationContext
     */
    public void setApplicationContext(final ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }
}
